package com.varton.theory;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 *
 * @author lihuazeng
 */
public class Proxy {
    public static Object newInstance(MyClassLoader classLoader, Class<?>[] interfaces, InvocationHandler handler){
        //生产java源码
        String srcPath = generateJavaSrc(interfaces, handler);
        //编译成字节码class
        String classPath = compile(srcPath);
        //类加载器加载
        Class aClass = load(classLoader, classPath);
        //生成实例
        Object o = null;
        try {
            Constructor c = aClass.getConstructor(handler.getClass());
            c.setAccessible(true);
            o = c.newInstance(handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return o;
    }

    private static Class load(MyClassLoader classLoader, String classPath) {
        return classLoader.getProxyObjClass(classPath);
    }

    private static String compile(String srcPath) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager javaFileManager = compiler.getStandardFileManager(null, null, null);
        File file = new File(srcPath);
        Iterable<? extends JavaFileObject> iterable = javaFileManager.getJavaFileObjects(file);
        JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, null, null ,null, iterable);
        task.call();
        file.delete();
        return srcPath.replace("java","class");
    }

    private static String generateJavaSrc(Class<?>[] interfaces, InvocationHandler handler) {
        final String left = "{";
        final String right = "}";
        final String l = "(";
        final String r = ")";
        final String d = ";";
        final String n = "\r\n";
        final String whiteSpace = " ";
        final String w = ",";
        StringBuilder builder = new StringBuilder();
        builder.append("package com.varton.theory;").append(n);
        builder.append("import java.lang.reflect.Method;").append(n);
        builder.append("public class Proxy$10 implements ").append(n);
        builder.append(interfaces[0].getName()).append(left).append(n);

        builder.append("private ").append(handler.getClass().getName()).append(whiteSpace).append("h").append(d).append(n);
        builder.append("public ").append("Proxy$10").append(l).append(handler.getClass().getName()).append(whiteSpace).append("h").append(r).append(left).append(n);
        builder.append("this.h=h;").append(right).append(n);
        Method[] methods = interfaces[0].getMethods();
        for (Method m : methods){
            builder.append("public ").append(m.getReturnType().getName()).append(whiteSpace).append(m.getName()).append(l);
            Parameter[] parameters = m.getParameters();
            if (parameters != null){
                int i = parameters.length;
                for (Parameter p : parameters){
                    builder.append(p.getClass()).append(whiteSpace).append(p.getName());
                    i--;
                    if (i != 0){
                        builder.append(w);
                    }
                }
            }
            builder.append(r).append(left).append(n);
            builder.append("try {").append(n);
            builder.append("Method m = ").append(interfaces[0].getName()).append(".class.getMethod(").append("\"").append(m.getName()).append("\"").append(w).append("new Class[]{});").append(n);
            builder.append("this.h.invoke(this, m, null);").append(n);
            builder.append("} catch (Throwable throwable) {").append(right).append(n);
            builder.append(right).append(n);
        }

        builder.append(right);
        String src = builder.toString();
        try {
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(new File(Proxy.class.getResource("").getPath()+"/Proxy$10.java")));
            outputStreamWriter.write(src);
            outputStreamWriter.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Proxy.class.getResource("").getPath()+"/Proxy$10.java";
    }
}